// Package check 提供结构体字段校验功能，支持多种数据类型和校验规则
package check

import (
	"errors"
	"reflect"
	"strconv"
)

// StructCheck 对给定的结构体进行字段校验
// st: 需要校验的结构体指针
// kmap: 包含校验数据的map
// errlist: 错误列表
// 返回: 包含所有校验错误的列表
func StructCheck(st interface{}, kmap map[string]interface{}, errlist []error) []error {
	var typeInfo = reflect.TypeOf(st).Elem()
	var valInfo = reflect.ValueOf(st).Elem()
	return structinfo("$", kmap, typeInfo, valInfo, errlist)
}

// getJsonTag 从结构体标签中提取json字段名
// tag: 结构体标签
// 返回: json字段名，如果不存在则返回空字符串
func getJsonTag(tag reflect.StructTag) string {
	tagStr := tag.Get("json")
	if tagStr == "" {
		return ""
	}
	return tagStr
}

// structinfo 递归校验结构体字段
// name: 当前字段的完整路径名
// kmap: 包含校验数据的map
// t: 结构体类型信息
// v: 结构体值信息
// errList: 错误列表
// 返回: 更新后的错误列表
func structinfo(name string, kmap map[string]interface{}, t reflect.Type, v reflect.Value, errList []error) []error {
	structSet := make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldValue := v.Field(i)
		val := fieldValue.Interface()
		kind := fieldValue.Kind().String()
		tag := field.Tag
		jsonTag := getJsonTag(tag)
		if _, ok := kmap[jsonTag]; !ok {
			errList = append(errList, errors.New(name+"."+jsonTag+":未返回"))
			continue
		}
		structSet[jsonTag] = 1
		if kind == "struct" || kind == "interface" || kind == "map" {
			kmm, isok := kmap[jsonTag].(map[string]interface{})
			if isok {
				errList = structinfo(name+"."+jsonTag, kmm, v.Field(i).Type(), v.Field(i), errList)
			} else {
				errList = checkTag(kind, name+"."+jsonTag, val, t.Field(i).Tag, errList)
			}
		} else {
			if kind == "slice" || kind == "array" {
				errList = checkSlice(name+"."+jsonTag, v.Field(i).Len(), tag, errList)
				kll, ok := kmap[jsonTag].([]interface{})
				if !ok {
					errList = append(errList, errors.New(name+"."+jsonTag+": 类型不匹配，期望slice"))
					continue
				}
				for ii := 0; ii < v.Field(i).Len(); ii++ {
					kkind := v.Field(i).Index(ii).Kind().String()
					kkey := name + "." + jsonTag + "[" + strconv.Itoa(ii) + "]"
					if kkind == "struct" || kkind == "interface" || kkind == "map" || kkind == "slice" || kkind == "array" {
						kll2, isok := kll[ii].(map[string]interface{})
						if isok {
							errList = structinfo(kkey, kll2, v.Field(i).Index(ii).Type(), v.Field(i).Index(ii), errList)
						} else {
							errList = checkTag(kkind, kkey, v.Field(i).Index(ii).Interface(), t.Field(i).Tag, errList)
						}
					} else {
						errList = checkTag(kkind, kkey, v.Field(i).Index(ii).Interface(), t.Field(i).Tag, errList)
					}
				}
			} else {
				errList = checkTag(kind, name+"."+jsonTag, val, t.Field(i).Tag, errList)
			}
		}
	}
	for k := range kmap {
		if _, ok := structSet[k]; !ok {
			errList = append(errList, errors.New(name+"."+k+":多返回了"))
		}
	}
	return errList
}
